返回 筑基・Web 道途启关

20我的第一个上线项目:从"本地跑通"到"用户能用"

博主
大约 18 分钟

20我的第一个上线项目:从"本地跑通"到"用户能用"

"为什么生产环境显示不出来?我本地明明好好的!"凌晨两点,我在空荡荡的办公室里对着屏幕咆哮。距离产品上线还有6小时,前端页面一片空白,而我连Nginx是什么都不知道。从开发到上线,原来隔着十万八千个坑。

image-20260202161611181

一、前端实战:从"凑合能用"到"生产就绪"

1.1 我的"完美"前端页面

第一次写前端页面,我觉得自己是个天才:

html

<!-- 我写的"完美"首页 -->
<!DOCTYPE html>
<html>
<head>
    <title>员工管理系统</title>
    <style>
        /* 300行CSS直接写在这里,维护?不存在的 */
        body { margin: 0; padding: 0; font-family: Arial; }
        .header { background: blue; color: white; height: 60px; }
        /* 还有298行... */
    </style>
</head>
<body>
    <div class="header">员工管理系统</div>
    <div class="content">
        <!-- 200行HTML,所有逻辑都堆在这里 -->
    </div>
    
    <script>
        // 500行JavaScript也放这里
        function loadEmployees() {
            // 直接写死URL,多方便啊!
            fetch('http://localhost:8080/api/employees')
                .then(response => response.json())
                .then(data => {
                    // 直接在HTML里拼接字符串
                    let html = '';
                    data.forEach(emp => {
                        html += `<tr><td>${emp.name}</td><td>${emp.age}</td></tr>`;
                    });
                    document.getElementById('table-body').innerHTML = html;
                });
        }
        
        // 页面加载完就调用
        window.onload = loadEmployees;
    </script>
</body>
</html>

看起来能跑,直到我遇到了这些问题

  1. 维护困难:1000行代码在一个文件里,改一个地方可能影响十个地方
  2. 加载缓慢:所有CSS、JS、HTML都在一个文件,首次加载要5秒
  3. 代码重复:每个页面都复制粘贴同样的header和footer代码
  4. 调试困难:报错只知道在几百行的某个地方

image-20260202162055987

1.2 导师的"生产级"前端项目结构

导师给我看了他们的项目结构:

text

src/
├── assets/           # 静态资源
│   ├── images/      # 图片
│   ├── fonts/       # 字体
│   └── styles/      # 全局样式
├── components/       # 组件
│   ├── Header.vue
│   ├── Footer.vue
│   ├── SearchBar.vue
│   └── Pagination.vue
├── views/            # 页面
│   ├── Home.vue
│   ├── EmployeeList.vue
│   ├── EmployeeDetail.vue
│   └── Login.vue
├── api/              # 接口封装
│   ├── employee.js
│   ├── department.js
│   └── auth.js
├── utils/            # 工具函数
│   ├── request.js    # axios封装
│   ├── validate.js   # 表单验证
│   └── format.js     # 数据格式化
├── router/           # 路由配置
│   └── index.js
├── store/            # 状态管理
│   └── index.js
└── App.vue           # 根组件

更重要的是,他教我怎么封装axios:

javascript

// utils/request.js - 生产级的请求封装
import axios from 'axios';
import { Message } from 'element-ui';
import router from '@/router';

// 创建axios实例
const service = axios.create({
  baseURL: process.env.VUE_APP_API_BASE_URL || '/api',
  timeout: 30000, // 30秒超时
  headers: {
    'Content-Type': 'application/json;charset=UTF-8'
  }
});

// 请求拦截器
service.interceptors.request.use(
  config => {
    // 添加token
    const token = localStorage.getItem('token');
    if (token) {
      config.headers['Authorization'] = `Bearer ${token}`;
    }
    
    // 记录请求日志(开发环境)
    if (process.env.NODE_ENV === 'development') {
      console.log(`[请求] ${config.method.toUpperCase()} ${config.url}`, config.data || config.params);
    }
    
    return config;
  },
  error => {
    console.error('请求配置错误:', error);
    return Promise.reject(error);
  }
);

// 响应拦截器
service.interceptors.response.use(
  response => {
    const res = response.data;
    
    // 根据后端接口约定判断
    if (res.code === 200) {
      return res.data;
    } else if (res.code === 401) {
      // token过期,跳转到登录页
      Message.error('登录已过期,请重新登录');
      localStorage.removeItem('token');
      localStorage.removeItem('userInfo');
      router.push('/login');
      return Promise.reject(new Error('未授权'));
    } else if (res.code === 403) {
      Message.error('权限不足');
      return Promise.reject(new Error('权限不足'));
    } else {
      // 其他错误
      Message.error(res.msg || '请求失败');
      return Promise.reject(new Error(res.msg || '请求失败'));
    }
  },
  error => {
    console.error('请求失败:', error);
    
    if (error.response) {
      // 服务器返回了错误状态码
      switch (error.response.status) {
        case 400:
          Message.error('请求参数错误');
          break;
        case 401:
          Message.error('未授权,请登录');
          router.push('/login');
          break;
        case 403:
          Message.error('拒绝访问');
          break;
        case 404:
          Message.error('请求资源不存在');
          break;
        case 500:
          Message.error('服务器内部错误');
          break;
        case 502:
          Message.error('网关错误');
          break;
        case 503:
          Message.error('服务不可用');
          break;
        default:
          Message.error(`请求错误: ${error.response.status}`);
      }
    } else if (error.request) {
      // 请求发送了但没有收到响应
      if (error.message.includes('timeout')) {
        Message.error('请求超时,请检查网络');
      } else {
        Message.error('网络错误,请检查连接');
      }
    } else {
      // 其他错误
      Message.error('请求失败: ' + error.message);
    }
    
    return Promise.reject(error);
  }
);

// 导出常用的请求方法
export default {
  // GET请求
  get(url, params) {
    return service.get(url, { params });
  },
  
  // POST请求
  post(url, data) {
    return service.post(url, data);
  },
  
  // PUT请求
  put(url, data) {
    return service.put(url, data);
  },
  
  // DELETE请求
  delete(url) {
    return service.delete(url);
  },
  
  // 文件上传
  upload(url, file, onProgress) {
    const formData = new FormData();
    formData.append('file', file);
    
    return service.post(url, formData, {
      headers: {
        'Content-Type': 'multipart/form-data'
      },
      onUploadProgress: onProgress
    });
  },
  
  // 下载文件
  download(url, params, filename) {
    return service.get(url, {
      params,
      responseType: 'blob'
    }).then(response => {
      const blob = new Blob([response.data]);
      const url = window.URL.createObjectURL(blob);
      const link = document.createElement('a');
      link.href = url;
      link.download = filename;
      link.click();
      window.URL.revokeObjectURL(url);
    });
  }
};

使用封装后的API:

javascript

// api/employee.js
import request from '@/utils/request';

export default {
  // 分页查询员工
  getEmployees(params) {
    return request.get('/employees', params);
  },
  
  // 获取员工详情
  getEmployee(id) {
    return request.get(`/employees/${id}`);
  },
  
  // 添加员工
  addEmployee(data) {
    return request.post('/employees', data);
  },
  
  // 更新员工
  updateEmployee(id, data) {
    return request.put(`/employees/${id}`, data);
  },
  
  // 删除员工
  deleteEmployee(id) {
    return request.delete(`/employees/${id}`);
  },
  
  // 批量删除
  batchDelete(ids) {
    return request.delete('/employees/batch', { ids });
  },
  
  // 导出员工数据
  exportEmployees(params, filename) {
    return request.download('/employees/export', params, filename);
  }
};

// 在Vue组件中使用
export default {
  data() {
    return {
      employees: [],
      loading: false
    }
  },
  
  methods: {
    async loadEmployees() {
      this.loading = true;
      try {
        const params = {
          page: this.currentPage,
          size: this.pageSize,
          name: this.searchName,
          deptId: this.selectedDept
        };
        
        const data = await employeeApi.getEmployees(params);
        this.employees = data.list;
        this.total = data.total;
      } catch (error) {
        console.error('加载员工列表失败:', error);
      } finally {
        this.loading = false;
      }
    }
  }
}

二、本地部署:从"我电脑能跑"到"别人也能跑"

image-20260202162246261

2.1 第一次给同事演示的尴尬

"看我做的员工管理系统,牛逼不?"我得意洋洋地给同事演示。

他打开浏览器,输入http://localhost:5500,页面一片空白。

"你是不是没启动后端?"我问。

"后端?什么后端?你不是说打开就能用吗?"

问题:我从来没想过,别人要在自己的电脑上运行我的项目。

2.2 配置开发环境的血泪史

我写的"README.md":

text

# 员工管理系统
运行方法:
1. 启动后端:在IDEA里运行Application.java
2. 启动前端:用VSCode打开,安装Live Server插件,点击Go Live

同事的反馈:

  1. "我用的Eclipse,怎么运行?"
  2. "Live Server是什么?怎么安装?"
  3. "数据库怎么配置?"
  4. "为什么我连不上数据库?"

2.3 完整的开发环境配置指南

后来我写了一个真正的README:

markdown

# 员工管理系统 - 开发环境搭建指南

## 环境要求
- JDK 11+
- Node.js 14+
- MySQL 8.0+
- Maven 3.6+

## 一、后端项目

### 1. 数据库配置
```sql
-- 创建数据库
CREATE DATABASE tlias CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- 执行初始化脚本
mysql -u root -p tlias < sql/init.sql

2. 修改配置文件

复制application-example.ymlapplication.yml,修改数据库连接信息:

yaml

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/tlias?useSSL=false&serverTimezone=Asia/Shanghai
    username: your_username
    password: your_password

3. 启动项目

bash

# 方式1:使用Maven
mvn spring-boot:run

# 方式2:打包后运行
mvn clean package
java -jar target/tlias-0.0.1-SNAPSHOT.jar

二、前端项目

1. 安装依赖

bash

npm install
# 或使用淘宝镜像
npm install --registry=https://registry.npm.taobao.org

2. 环境配置

复制.env.example.env.development

env

VUE_APP_API_BASE_URL=http://localhost:8080/api
VUE_APP_TITLE=员工管理系统(开发环境)

3. 启动项目

bash

# 开发环境(热重载)
npm run serve

# 构建生产版本
npm run build

三、常见问题

Q1:端口被占用

bash

# Windows
netstat -ano | findstr :8080
taskkill /PID [PID] /F

# Linux/Mac
lsof -i:8080
kill -9 [PID]

Q2:数据库连接失败

  1. 检查MySQL是否启动:systemctl status mysql
  2. 检查用户名密码是否正确
  3. 检查防火墙是否开放3306端口

Q3:前端跨域问题

后端已配置CORS,如仍有问题,检查:

  1. 后端是否启动
  2. 请求URL是否正确
  3. 浏览器控制台是否有错误

四、开发工具推荐

  • IDE:IntelliJ IDEA / VS Code
  • 数据库工具:Navicat / DBeaver
  • API测试:Postman / Insomnia
  • Git客户端:SourceTree / GitKraken

text

## 三、服务器部署:从"本地王者"到"线上菜鸟"

### 3.1 第一次上线:全军覆没

产品经理:"明天要上线,今天部署到测试环境。"

我:"好的,没问题!"(内心:服务器是什么?怎么部署?)

我的"部署流程":
1. 把jar包用微信发给了运维
2. 把前端文件压缩包也发过去
3. "你帮我放到服务器上,谢谢!"

结果:
1. 服务器没有Java环境
2. 数据库连不上
3. 前端访问不了后端
4. 没有日志,不知道哪里错了

### 3.2 完整的服务器部署手册

经过这次教训,我整理了一套完整的部署流程:

#### 环境准备脚本

​```bash
#!/bin/bash
# deploy/prepare.sh - 服务器环境准备脚本

set -e  # 遇到错误退出

echo "=== 开始准备服务器环境 ==="

# 1. 安装JDK
echo "1. 安装JDK 11..."
if ! command -v java &> /dev/null; then
    yum install -y java-11-openjdk-devel
    echo "JDK安装完成"
else
    echo "JDK已安装: $(java -version 2>&1 | head -1)"
fi

# 2. 安装MySQL
echo "2. 安装MySQL 8.0..."
if ! command -v mysql &> /dev/null; then
    wget https://dev.mysql.com/get/mysql80-community-release-el7-7.noarch.rpm
    rpm -ivh mysql80-community-release-el7-7.noarch.rpm
    yum install -y mysql-community-server
    systemctl start mysqld
    systemctl enable mysqld
    echo "MySQL安装完成"
    
    # 获取初始密码
    temp_password=$(grep 'temporary password' /var/log/mysqld.log | awk '{print $NF}')
    echo "初始密码: $temp_password"
    echo "请使用 mysql_secure_installation 进行安全设置"
else
    echo "MySQL已安装: $(mysql --version)"
fi

# 3. 安装Nginx
echo "3. 安装Nginx..."
if ! command -v nginx &> /dev/null; then
    yum install -y nginx
    systemctl start nginx
    systemctl enable nginx
    echo "Nginx安装完成"
else
    echo "Nginx已安装: $(nginx -v 2>&1)"
fi

# 4. 创建应用目录
echo "4. 创建应用目录..."
mkdir -p /opt/tlias/{app,logs,backup,config}
chmod 755 /opt/tlias

# 5. 创建应用用户
echo "5. 创建应用用户..."
if ! id -u tlias &> /dev/null; then
    useradd -r -s /bin/false tlias
    chown -R tlias:tlias /opt/tlias
    echo "用户创建完成"
fi

echo "=== 环境准备完成 ==="

数据库初始化脚本

sql

-- deploy/init-db.sql
CREATE DATABASE IF NOT EXISTS `tlias` 
CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

USE `tlias`;

-- 部门表
CREATE TABLE `dept` (
    `id` bigint NOT NULL AUTO_INCREMENT,
    `name` varchar(50) NOT NULL COMMENT '部门名称',
    `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
    `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    `is_deleted` tinyint DEFAULT '0',
    PRIMARY KEY (`id`),
    UNIQUE KEY `uk_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='部门表';

-- 员工表
CREATE TABLE `emp` (
    `id` bigint NOT NULL AUTO_INCREMENT,
    `name` varchar(50) NOT NULL COMMENT '姓名',
    `gender` tinyint DEFAULT '1' COMMENT '性别:1男,2女',
    `age` int DEFAULT NULL COMMENT '年龄',
    `dept_id` bigint DEFAULT NULL COMMENT '部门ID',
    `entrydate` date DEFAULT NULL COMMENT '入职日期',
    `avatar` varchar(255) DEFAULT NULL COMMENT '头像URL',
    `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
    `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    `is_deleted` tinyint DEFAULT '0',
    PRIMARY KEY (`id`),
    KEY `idx_dept_id` (`dept_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='员工表';

-- 初始化数据
INSERT INTO `dept` (`name`) VALUES 
('总裁办'),
('技术部'),
('市场部'),
('人事部'),
('财务部');

INSERT INTO `emp` (`name`, `gender`, `age`, `dept_id`, `entrydate`) VALUES
('张三', 1, 25, 1, '2023-01-01'),
('李四', 1, 30, 2, '2022-06-01'),
('王五', 2, 28, 3, '2023-03-15'),
('赵六', 2, 26, 4, '2022-12-01');

Nginx配置

nginx

# /etc/nginx/conf.d/tlias.conf
upstream tlias_backend {
    server 127.0.0.1:8080;
    # 可以配置多个后端实现负载均衡
    # server 127.0.0.1:8081;
    # server 127.0.0.1:8082;
}

server {
    listen 80;
    server_name your-domain.com;  # 你的域名
    charset utf-8;
    
    # 前端静态资源
    location / {
        root /opt/tlias/frontend/dist;
        index index.html;
        
        # 解决Vue/React路由刷新404问题
        try_files $uri $uri/ /index.html;
        
        # 开启gzip压缩
        gzip on;
        gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
        gzip_min_length 1024;
        
        # 缓存静态资源
        location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
            expires 1y;
            add_header Cache-Control "public, immutable";
        }
    }
    
    # 后端API代理
    location /api/ {
        proxy_pass http://tlias_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        
        # 超时设置
        proxy_connect_timeout 30s;
        proxy_send_timeout 30s;
        proxy_read_timeout 30s;
        
        # 上传文件大小限制
        client_max_body_size 10m;
    }
    
    # 健康检查
    location /health {
        access_log off;
        return 200 "healthy\n";
    }
    
    # 禁止访问隐藏文件
    location ~ /\. {
        deny all;
    }
}

Systemd服务配置

ini

# /etc/systemd/system/tlias.service
[Unit]
Description=Tlias Employee Management System
After=network.target mysql.service
Wants=mysql.service

[Service]
Type=simple
User=tlias
Group=tlias
WorkingDirectory=/opt/tlias/app

# 启动命令
ExecStart=/usr/bin/java \
    -Xms512m -Xmx1024m \
    -XX:+UseG1GC \
    -XX:MaxGCPauseMillis=200 \
    -XX:+HeapDumpOnOutOfMemoryError \
    -XX:HeapDumpPath=/opt/tlias/logs/heapdump.hprof \
    -Djava.security.egd=file:/dev/./urandom \
    -Dfile.encoding=UTF-8 \
    -jar /opt/tlias/app/tlias.jar \
    --spring.config.location=/opt/tlias/config/application.yml \
    --logging.file.path=/opt/tlias/logs

# 重启策略
Restart=always
RestartSec=10

# 资源限制
LimitNOFILE=65536
LimitNPROC=4096

# 标准输出重定向
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

部署脚本

bash

#!/bin/bash
# deploy/deploy.sh - 一键部署脚本

set -e

echo "=== 开始部署 Tlias 系统 ==="

# 1. 停止现有服务
echo "1. 停止现有服务..."
systemctl stop tlias.service 2>/dev/null || true

# 2. 备份当前版本
echo "2. 备份当前版本..."
backup_dir="/opt/tlias/backup/$(date +%Y%m%d_%H%M%S)"
mkdir -p $backup_dir
cp -r /opt/tlias/app/* $backup_dir/ 2>/dev/null || true

# 3. 清理应用目录
echo "3. 清理应用目录..."
rm -rf /opt/tlias/app/*
mkdir -p /opt/tlias/app

# 4. 部署后端
echo "4. 部署后端..."
cp target/tlias-*.jar /opt/tlias/app/tlias.jar
cp src/main/resources/application-prod.yml /opt/tlias/config/application.yml

# 5. 部署前端
echo "5. 部署前端..."
rm -rf /opt/tlias/frontend/*
mkdir -p /opt/tlias/frontend
cp -r frontend/dist/* /opt/tlias/frontend/

# 6. 设置权限
echo "6. 设置权限..."
chown -R tlias:tlias /opt/tlias
chmod 755 /opt/tlias/app/tlias.jar

# 7. 启动服务
echo "7. 启动服务..."
systemctl daemon-reload
systemctl start tlias.service
systemctl enable tlias.service

# 8. 检查服务状态
echo "8. 检查服务状态..."
sleep 5
if systemctl is-active --quiet tlias.service; then
    echo "✅ 服务启动成功"
    echo "服务状态:"
    systemctl status tlias.service --no-pager
else
    echo "❌ 服务启动失败"
    echo "查看日志: journalctl -u tlias.service -n 50"
    exit 1
fi

# 9. 重启Nginx
echo "9. 重启Nginx..."
nginx -t  # 测试配置
systemctl reload nginx

echo "=== 部署完成 ==="
echo "前端访问: http://your-domain.com"
echo "后端API: http://your-domain.com/api"
echo "查看日志: tail -f /opt/tlias/logs/spring.log"

四、运维监控:从"盲人摸象"到"明察秋毫"

image-20260202163030294

4.1 第一次线上故障:一夜白头

凌晨3点,用户反馈"系统打不开了"。

我做了什么:

  1. 重启服务(没好)
  2. 重启服务器(没好)
  3. 检查日志(日志文件是空的)

问题:我没有任何监控,不知道系统状态,不知道哪里出错。

4.2 建立监控体系

后来我建立了一套完整的监控:

健康检查接口

java

@RestController
@RequestMapping("/actuator")
public class HealthController {
    
    @Autowired
    private DataSource dataSource;
    
    @GetMapping("/health")
    public Map<String, Object> health() {
        Map<String, Object> health = new HashMap<>();
        
        // 应用状态
        health.put("status", "UP");
        health.put("timestamp", System.currentTimeMillis());
        
        // 数据库状态
        Map<String, Object> dbStatus = new HashMap<>();
        try {
            dataSource.getConnection().close();
            dbStatus.put("status", "UP");
        } catch (Exception e) {
            dbStatus.put("status", "DOWN");
            dbStatus.put("error", e.getMessage());
        }
        health.put("database", dbStatus);
        
        // 系统信息
        Runtime runtime = Runtime.getRuntime();
        Map<String, Object> systemInfo = new HashMap<>();
        systemInfo.put("memory", String.format("已用: %dMB / 总共: %dMB",
            (runtime.totalMemory() - runtime.freeMemory()) / 1024 / 1024,
            runtime.totalMemory() / 1024 / 1024));
        systemInfo.put("cpu", Runtime.getRuntime().availableProcessors());
        systemInfo.put("uptime", ManagementFactory.getRuntimeMXBean().getUptime());
        health.put("system", systemInfo);
        
        return health;
    }
}

日志配置

yaml

# application-prod.yml
logging:
  file:
    path: /opt/tlias/logs
    name: ${logging.file.path}/spring.log
  level:
    root: INFO
    com.company.tlias: DEBUG
    org.springframework.web: WARN
    org.hibernate: WARN
  
  # 日志轮转配置
  logback:
    rollingpolicy:
      max-file-size: 10MB
      max-history: 30
      total-size-cap: 3GB
  
  pattern:
    file: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
    console: "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"

监控脚本

bash

#!/bin/bash
# monitor/check.sh - 系统监控脚本

# 检查服务状态
check_service() {
    if systemctl is-active --quiet tlias.service; then
        echo "✅ Tlias服务运行正常"
    else
        echo "❌ Tlias服务已停止"
        systemctl status tlias.service --no-pager
        return 1
    fi
}

# 检查数据库连接
check_database() {
    response=$(curl -s http://localhost:8080/actuator/health)
    status=$(echo $response | grep -o '"status":"[^"]*"' | cut -d'"' -f4)
    
    if [ "$status" = "UP" ]; then
        echo "✅ 数据库连接正常"
    else
        echo "❌ 数据库连接失败"
        return 1
    fi
}

# 检查磁盘空间
check_disk() {
    usage=$(df -h /opt | tail -1 | awk '{print $5}' | sed 's/%//')
    if [ $usage -gt 80 ]; then
        echo "⚠️  磁盘使用率过高: ${usage}%"
        return 1
    else
        echo "✅ 磁盘空间正常: ${usage}%"
    fi
}

# 检查内存使用
check_memory() {
    free_mb=$(free -m | awk '/^Mem:/{print $4}')
    if [ $free_mb -lt 100 ]; then
        echo "⚠️  内存不足: ${free_mb}MB可用"
        return 1
    else
        echo "✅ 内存充足: ${free_mb}MB可用"
    fi
}

# 检查日志文件大小
check_logs() {
    log_size=$(du -m /opt/tlias/logs/spring.log 2>/dev/null | cut -f1)
    if [ -z "$log_size" ]; then
        echo "⚠️  日志文件不存在"
    elif [ $log_size -gt 100 ]; then
        echo "⚠️  日志文件过大: ${log_size}MB"
    else
        echo "✅ 日志文件正常: ${log_size}MB"
    fi
}

# 发送告警(示例:发送到钉钉)
send_alert() {
    local message=$1
    curl -s "$DINGDING_WEBHOOK" \
        -H 'Content-Type: application/json' \
        -d "{\"msgtype\": \"text\", \"text\": {\"content\": \"[Tlias告警] $message\"}}"
}

# 主函数
main() {
    echo "=== Tlias系统监控检查 ==="
    echo "时间: $(date)"
    
    errors=()
    
    if ! check_service; then errors+=("服务异常"); fi
    if ! check_database; then errors+=("数据库异常"); fi
    if ! check_disk; then errors+=("磁盘空间不足"); fi
    if ! check_memory; then errors+=("内存不足"); fi
    check_logs
    
    if [ ${#errors[@]} -eq 0 ]; then
        echo "=== 所有检查通过 ==="
    else
        echo "=== 发现 ${#errors[@]} 个问题 ==="
        for error in "${errors[@]}"; do
            echo "❌ $error"
        done
        
        # 发送告警
        if [ -n "$DINGDING_WEBHOOK" ]; then
            send_alert "系统异常: ${errors[*]}"
        fi
        
        exit 1
    fi
}

# 定时执行(通过crontab)
# */5 * * * * /opt/tlias/monitor/check.sh >> /opt/tlias/logs/monitor.log 2>&1

main

五、经验总结:从开发者到工程师的蜕变

image-20260202163232922

5.1 我学到的教训

  1. 开发环境 ≠ 测试环境 ≠ 生产环境:三者的差异是线上问题的根源
  2. 没有监控就是裸奔:不知道系统状态的系统不可靠
  3. 文档不是可选的:没有文档的项目难以维护
  4. 自动化是必须的:手动部署一定会出错

5.2 我的部署清单

现在每次部署,我都会检查:

  • 代码是否通过所有测试
  • 数据库脚本是否已准备好
  • 配置文件是否已更新
  • 依赖版本是否正确
  • 监控是否已配置
  • 回滚方案是否已准备
  • 团队是否已通知

5.3 如果重来一次

我会:

  1. 早建监控:从第一天就开始监控
  2. 自动化一切:部署、测试、备份都自动化
  3. 重视文档:开发文档、部署文档、故障处理文档
  4. 建立流程:代码审查、测试、部署、监控的完整流程

结语:从写代码到做产品

从那个在凌晨两点对着空白屏幕绝望的新手,到现在能从容处理线上故障的工程师,我最大的感悟是:

技术只是工具,解决问题才是目的。

一个成功的项目,不是写了多少行代码,用了多少新技术,而是:

  • 用户能不能顺畅使用
  • 系统能不能稳定运行
  • 团队能不能高效协作
  • 问题能不能快速解决

记住:真正的工程师价值,不在于解决了多少技术难题,而在于为用户创造了多少价值。

知识点测试

读完文章了?来测试一下你对知识点的掌握程度吧!

评论区

使用 GitHub 账号登录后即可发表评论,支持 Markdown 格式。

如果评论系统无法加载,请确保:

  • 您的网络可以访问 GitHub
  • giscus GitHub App 已安装到仓库
  • 仓库已启用 Discussions 功能